#include <GameEngine/UnitTest/unit_test.h>

#include <GameEngine/Geometry/Surfaces/box2d.h>


#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif


//! Launch the unit tests on Box2d objects
/*!
 @return True if all unit tests on Box2d objects were
         succesful, false otherwise
 */
template <class T>
bool UnitTest::test_geometry_box2d_functions() {
	bool res = true;

	// Get number of bytes
	MemoryManager::BytesCount bytes = MemoryManager::instance()->bytes();

	// Create box
	T x_min = (T)0, x_max = (T)4;
	T y_min = (T)0, y_max = (T)4;
	GameEngine::Box2d<T> box(x_min,x_max,y_min,y_max);

	// Test min / max functions
	T tolerance = (T)0.00001;
	{{
		res &= UNIT_TEST_ERROR( "[Box2d] Test min_x()", ( abs(box.min_x()-x_min) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test max_x()", ( abs(box.max_x()-x_max) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test min_y()", ( abs(box.min_y()-y_min) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test max_y()", ( abs(box.max_y()-y_max) < tolerance ) );
	}}

	// Test point functions
	{{
		res &= UNIT_TEST_ERROR( "[Box2d] Test bottom_left()",  ( GameEngine::Point2d<T>(x_min,y_min).get_distance(box.bottom_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test bottom_right()", ( GameEngine::Point2d<T>(x_max,y_min).get_distance(box.bottom_right()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test top_left()",     ( GameEngine::Point2d<T>(x_min,y_max).get_distance(box.top_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test top_right()",    ( GameEngine::Point2d<T>(x_max,y_max).get_distance(box.top_right()) < tolerance ) );
	}}

	// Test point function
	{{
		res &= UNIT_TEST_ERROR( "[Box2d] Test point(0,0)", ( box.point(GameEngine::Coords2d<T>((T)0,(T)0)).get_distance(box.bottom_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test point(1,0)", ( box.point(GameEngine::Coords2d<T>((T)1,(T)0)).get_distance(box.bottom_right()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test point(0,1)", ( box.point(GameEngine::Coords2d<T>((T)0,(T)1)).get_distance(box.top_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test point(1,1)", ( box.point(GameEngine::Coords2d<T>((T)1,(T)1)).get_distance(box.top_right()) < tolerance ) );
	}}

	// Test coordinates function
	{{
		GameEngine::Coords2d<T> c0 = box.coordinates(box.bottom_left());
		GameEngine::Coords2d<T> c1 = box.coordinates(box.bottom_right());
		GameEngine::Coords2d<T> c2 = box.coordinates(box.top_left());
		GameEngine::Coords2d<T> c3 = box.coordinates(box.top_right());
		res &= UNIT_TEST_ERROR( "[Box2d] Test coordinates(0,0)", ( GameEngine::Point2d<T>((T)0,(T)0).get_distance(Point2d<T>(c0.u(), c0.v())) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test coordinates(1,0)", ( GameEngine::Point2d<T>((T)1,(T)0).get_distance(Point2d<T>(c1.u(), c1.v())) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test coordinates(0,1)", ( GameEngine::Point2d<T>((T)0,(T)1).get_distance(Point2d<T>(c2.u(), c2.v())) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test coordinates(1,1)", ( GameEngine::Point2d<T>((T)1,(T)1).get_distance(Point2d<T>(c3.u(), c3.v())) < tolerance ) );
	}}

	// Test operator[] function
	{{
		res &= UNIT_TEST_ERROR( "[Box2d] Test operator[](First)", ( box[GameEngine::Box2D::First].get_distance(box.bottom_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test operator[](Second)", ( box[GameEngine::Box2D::Second].get_distance(box.top_left()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test operator[](Third)", ( box[GameEngine::Box2D::Third].get_distance(box.bottom_right()) < tolerance ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test operator[](Fourth)", ( box[GameEngine::Box2D::Fourth].get_distance(box.top_right()) < tolerance ) );
	}}

	// Test inside function
	{{
		GameEngine::Point2d<T> p0((T)1,(T)1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test inside()", ( box.inside(p0) == true ) );
		GameEngine::Point2d<T> p1((T)0,(T)0);
		res &= UNIT_TEST_ERROR( "[Box2d] Test inside()", ( box.inside(p1,false) == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test inside()", ( box.inside(p1,true) == false ) );
		GameEngine::Point2d<T> p2((T)6,(T)1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test inside()", ( box.inside(p2) == false ) );
	}}

	// Test box overlap function
	{{
		// Create box that crosses the other box
		GameEngine::Box2d<T> box0((T)1,(T)2,(T)-1,(T)5);
		res &= UNIT_TEST_ERROR( "[Box2d] Test box.overlap(box0)", ( box.overlap(box0) == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test box0.overlap(box)", ( box0.overlap(box) == true ) );

		// Create box that overlaps one axis of the other box
		GameEngine::Box2d<T> box1((T)-1,(T)2,(T)-1,(T)5);
		res &= UNIT_TEST_ERROR( "[Box2d] Test box.overlap(box1)", ( box.overlap(box1) == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test box1.overlap(box)", ( box1.overlap(box) == true ) );

		// Create box that overlaps two axes of the other box
		GameEngine::Box2d<T> box2((T)-1,(T)2,(T)2,(T)5);
		res &= UNIT_TEST_ERROR( "[Box2d] Test box.overlap(box2)", ( box.overlap(box2) == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test box2.overlap(box)", ( box2.overlap(box) == true ) );

		// Create box that does not cross the other box
		GameEngine::Box2d<T> box3((T)1,(T)2,(T)5,(T)7);
		res &= UNIT_TEST_ERROR( "[Box2d] Test box.overlap(box3)", ( box.overlap(box3) == false ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test box3.overlap(box)", ( box3.overlap(box) == false ) );

		// Create box that does not cross the other box
		GameEngine::Box2d<T> box4((T)5,(T)6,(T)5,(T)7);
		res &= UNIT_TEST_ERROR( "[Box2d] Test box.overlap(box4)", ( box.overlap(box4) == false ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test box4.overlap(box)", ( box4.overlap(box) == false ) );
	}}

	// Test line intersection function
	{{
		// Create box
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)0,(T)1);
		GameEngine::Box2d<T> box0_test(box0.min_x()-tolerance,box0.max_x()+tolerance,box0.min_y()-tolerance,box0.max_y()+tolerance);

		// Create line that crosses the box
		GameEngine::Line2d<T> line0(GameEngine::Point2d<T>((T)-1,(T)-1), GameEngine::Point2d<T>((T)1,(T)2));
		T coord1, coord2;
		GameEngine::IntersectionType::Type type;
		int nb = box0.intersection(line0, coord1, coord2, &type);
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( nb == 2 ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( type == GameEngine::IntersectionType::Intersection ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( box0_test.inside(line0.point(coord1)) == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( box0_test.inside(line0.point(coord2)) == true ) );

		// Create line that does not cross the box
		GameEngine::Line2d<T> line1(GameEngine::Point2d<T>((T)-1,(T)-1), GameEngine::Point2d<T>((T)-1,(T)2));
		nb = box0.intersection(line1, coord1, coord2, &type);
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( nb == 0 ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test intersection(line)", ( type == GameEngine::IntersectionType::NoIntersection ) );
	}}

	// Test closest_point function
	{{
		// Create box
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)0,(T)1);

		// Create outside point
		GameEngine::Point2d<T> p0((T)-0.5,(T)0.3);
		GameEngine::Coords2d<T> c0;
		bool test = box0.closest_point(p0, c0);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c0).get_distance( GameEngine::Point2d<T>((T)0,(T)0.3) ) < tolerance ) );

		// Create outside point
		GameEngine::Point2d<T> p1((T)-0.5,(T)-0.3);
		GameEngine::Coords2d<T> c1;
		test = box0.closest_point(p1, c1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c1).get_distance( GameEngine::Point2d<T>((T)0,(T)0) ) < tolerance ) );

		// Create outside point
		GameEngine::Point2d<T> p2((T)-0.5,(T)1.3);
		GameEngine::Coords2d<T> c2;
		test = box0.closest_point(p2, c2);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c2).get_distance( GameEngine::Point2d<T>((T)0,(T)1) ) < tolerance ) );

		// Create outside point
		GameEngine::Point2d<T> p3((T)1.5,(T)1.3);
		GameEngine::Coords2d<T> c3;
		test = box0.closest_point(p3, c3);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c3).get_distance( GameEngine::Point2d<T>((T)1,(T)1) ) < tolerance ) );

		// Create outside point
		GameEngine::Point2d<T> p4((T)1.5,(T)-0.3);
		GameEngine::Coords2d<T> c4;
		test = box0.closest_point(p4, c4);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c4).get_distance( GameEngine::Point2d<T>((T)1,(T)0) ) < tolerance ) );

		// Create inside point
		GameEngine::Point2d<T> p5((T)0.2,(T)0.3);
		GameEngine::Coords2d<T> c5;
		test = box0.closest_point(p5, c5);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( test == true ) );
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( box0.point(c5).get_distance( GameEngine::Point2d<T>((T)0,(T)0.3) ) < tolerance ) );
	}}

	// Test closest_point function
	{{
		// Create box
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)0,(T)1);

		// Create outside point
		GameEngine::Point2d<T> p0((T)-0.5,(T)0.3);
		GameEngine::Point2d<T> cp0 = box0.closest_point(p0);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( cp0.get_distance( GameEngine::Point2d<T>((T)0,(T)0.3) ) < tolerance ) );

		// Create outside point
		GameEngine::Point2d<T> p1((T)-0.5,(T)-0.3);
		GameEngine::Point2d<T> cp1 = box0.closest_point(p1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( cp1.get_distance( GameEngine::Point2d<T>((T)0,(T)0) ) < tolerance ) );

		// Create inside point
		GameEngine::Point2d<T> p2((T)0.2,(T)0.3);
		GameEngine::Point2d<T> cp2 = box0.closest_point(p2);
		res &= UNIT_TEST_ERROR( "[Box2d] Test closest_point()", ( cp2.get_distance( GameEngine::Point2d<T>((T)0,(T)0.3) ) < tolerance ) );
	}}

	// Test perimeter function
	{{
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)0,(T)1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test perimeter()", ( abs(box0.perimeter()-(T)4) < tolerance ) );
	}}

	// Test area function
	{{
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)0,(T)1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test area()", ( abs(box0.area()-(T)1) < tolerance ) );
	}}

	// Test degenerate function
	{{
		res &= UNIT_TEST_ERROR( "[Box2d] Test degenerate()", ( box.degenerate() == false ) );
		GameEngine::Box2d<T> box0((T)0,(T)1,(T)1,(T)1);
		res &= UNIT_TEST_ERROR( "[Box2d] Test degenerate()", ( box0.degenerate() == true ) );
	}}

	// Get number of bytes
	res &= UNIT_TEST_ERROR( "[Box2d] Test memory leak", ( bytes == MemoryManager::instance()->bytes() ));

	// Return result
	return res;
}


#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
